using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace WindowsApplication1
{
    public delegate void IncrementProgressDelegate();
    public delegate void ShowResultBitmapDelegate(Bitmap result);

    public partial class FuzzyISODataForm : Form
    {
        Bitmap source;
        public IncrementProgressDelegate incDelegate;
        public ShowResultBitmapDelegate showResultDelegate;
        
        public FuzzyISODataForm(string filename)
        {
            InitializeComponent();
            source = (Bitmap)Bitmap.FromFile(filename);
            pictureBox1.Image = source;
            incDelegate = new IncrementProgressDelegate(incrementProgressFunc);
            showResultDelegate = new ShowResultBitmapDelegate(showResultBitmap);

            if (filename.EndsWith(".bmp"))
                button3.Enabled = true;
            else
                button3.Enabled = false;
        }

        Bitmap clone(Bitmap b)
        {
            return new Bitmap(b);
        }

        ImageSegmentation currentSeg;

        private void button2_Click(object sender, EventArgs e)
        {
            FuzzyConfigForm form = new FuzzyConfigForm();
            //form.Parent = this;
            form.ShowDialog();
            if (form.result == DialogResult.OK)
            {
                currentSeg = new ImageSegmentation(clone(source), form.groups, form.color, form.epsilon, form.m, form.iterations);
                currentSeg.parentForm = this;

                Clustering clustering = new FuzzyISODataClustering();
                clustering.epsilon = form.epsilon;
                clustering.m = form.m;
                clustering.numberOfIterations = form.iterations;
                currentSeg.clustering = clustering;
                
                progressBar1.Maximum = form.iterations;

                button1.Enabled = false;
                button2.Enabled = false;
                Thread t = new Thread(new ThreadStart(run));
                t.Start();
            }
        }

        private void run()
        {
            Bitmap bmp = currentSeg.run();
            Invoke(showResultDelegate, new object[] { bmp });

        }

        public void incrementProgressFunc()
        {
            progressBar1.Increment(1);
            progressBar1.Invalidate();
        }

        public void showResultBitmap(Bitmap result)
        {
            TabPage page = new TabPage("Result " + tabControl1.TabPages.Count);
            PictureBox box = new PictureBox();
            box.Dock = DockStyle.Fill;
            box.Image = result;
            box.Parent = page;

            tabControl1.TabPages.Add(page);

            tabControl1.SelectTab(tabControl1.TabPages.Count - 1);
            progressBar1.Value = 0; 
            button1.Enabled = true;
            button2.Enabled = true;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            FuzzyConfigForm form = new FuzzyConfigForm();
            form.label4.Visible = false;
            form.textBox3.Visible = false;

            form.ShowDialog();
            if (form.result == DialogResult.OK)
            {
                currentSeg = new ImageSegmentation(clone(source), form.groups, form.color, form.epsilon, 1, form.iterations);
                currentSeg.parentForm = this;

                Clustering clustering = new ISODataClustering();
                clustering.epsilon = form.epsilon;
                clustering.numberOfIterations = form.iterations;
                currentSeg.clustering = clustering;

                progressBar1.Maximum = form.iterations;

                button1.Enabled = false;
                button2.Enabled = false;
                Thread t = new Thread(new ThreadStart(run));
                t.Start();
            }
        }

        private void button3_Click(object sender, EventArgs e)
        {
            double[] r4 = new double[4];
            double[] i3 = new double[3];

            PictureBox src_in = (PictureBox) tabControl1.SelectedTab.GetChildAtPoint(new Point(10, 10));
            Bitmap src_bmp = (Bitmap)src_in.Image;

            r4 = Ratios.RatiosRCH(src_bmp);
            i3 = Invariants.Invariant123(src_bmp);

            string info;

            info = "RC1: " + r4[0].ToString() + "\n" +
                    "RC2: " + r4[1].ToString() + "\n" +
                    "RC12: " + r4[2].ToString() + "\n" +
                    "Haralick: " + r4[3].ToString() + "\n" +

                    "I1: " + i3[0].ToString() + "\n" +
                    "I2: " + i3[1].ToString() + "\n" +
                    "I3: " + i3[2].ToString();

            Form ratio_results = new RatiosInvariantsForm(info, ref Ratios.itest);
            ratio_results.MdiParent = Editor.ActiveForm;
            ratio_results.Show();            
        }
    }

    public class ClusterSet
    {
        public RGBPointVector[] vectors;
        public double[][] mU;

        public ClusterSet(RGBPointVector[] vectors, int numberOfClusters)
        {
            this.vectors = vectors;
            randomizeClusters(numberOfClusters);
        }

        public RGBPointVector getVector(int i)
        {
            return vectors[i];
        }

        private int getNumberOfClusters()
        {
            return mU[0].Length;
        }

        public void setCluster(int vectorNumber, int clusterNumber)
        {
            for (int i = 0; i < getNumberOfClusters(); i++)
                mU[vectorNumber][i] = (i != clusterNumber ? 0.0D : 1.0D);
        }

        public int getNumberOfSamples()
        {
            return vectors.Length;
        }

        public Color getCluster(int sampleNumber)
        {
            int cluster = 0;
            for (int i = 0; i < getNumberOfClusters(); i++)
                if (mU[sampleNumber][i] > mU[sampleNumber][cluster])
                    cluster = i;

            /*
            byte b = (byte)(cluster & 0xFF); 
            byte g = (byte)((cluster & 0xFF00) >> 8);
            byte r = (byte)((cluster & 0xFF0000) >> 16);
            */
            return Color.FromArgb(cluster); ;
        }

        public RGBPointVector getClusterCenter(int clusterNumber, double m)
        {   
            RGBPointVector center = vectors[0].clone();
            center.mul(0.0D);
            double numberOfVectors = 0.0D;
            for(int i = 0; i < getNumberOfSamples(); i++)
            {
                double tmp = Math.Pow(mU[i][clusterNumber], m);
                numberOfVectors += tmp;
                RGBPointVector vTmp = vectors[i].clone();
                vTmp.mul(tmp);
                center.add(vTmp);
            }

            center.div((double)numberOfVectors);
            return center;
        }
        
        public RGBPointVector[] getClustersCenters(double m)
        {
            RGBPointVector[] centers = new RGBPointVector[getNumberOfClusters()];
            for(int i = 0; i < centers.Length; i++)
                centers[i] = getClusterCenter(i, m);

            return centers;
        }

        private void randomizeClusters(int numberOfClusters)
        {
            Random random = new Random();
            mU = new double[vectors.Length][];
            for (int i = 0; i < mU.Length; i++)
                mU[i] = new double[numberOfClusters];

            int avgVectorsForCluster = vectors.Length / numberOfClusters;
            int vectorNumber = 0;
            if (getNumberOfSamples() < 1000)
            {
                List<int> list = new List<int>();
                for (int i = 0; i < getNumberOfSamples(); i++)
                    list.Add(i);

                for (int clusterNumber = 0; clusterNumber < numberOfClusters; clusterNumber++)
                {
                    for (int i = 0; i < avgVectorsForCluster; i++)
                    {
                        int index = random.Next(getNumberOfSamples() - vectorNumber++);
                        int number = list[index];
                        setCluster(number, clusterNumber);
                        list.RemoveAt(index);
                    }

                }

                for (int i = 0; i < list.Count; i++)
                {
                    setCluster(list[i], random.Next(numberOfClusters));
                }

            }
            else
            {
                for (int i = 0; i < getNumberOfSamples(); i++)
                    setCluster(i, random.Next(numberOfClusters));

            }
        }
    }

    public class RGBPointVector
    {
        public double[] rgb;
        public int x;
        public int y;

        public RGBPointVector(Color px, int x, int y)
        {
            rgb = new double[] { (double)px.R, (double)px.G, (double)px.B };
            this.x = x;
            this.y = y;
        }

        public RGBPointVector(double[] d, int x, int y)
        {
            rgb = new double[d.Length];
            for (int i = 0; i < rgb.Length; i++)
                rgb[i] = d[i];

            this.x = x;
            this.y = y;
        }

        public void mul(double d)
        {
            for (int i = 0; i < rgb.Length; i++)
                rgb[i] *= d;
        }

        public void div(double d)
        {
            for (int i = 0; i < rgb.Length; i++)
                rgb[i] /= d;
        }

        public void add(RGBPointVector d)
        {
            for (int i = 0; i < rgb.Length; i++)
                rgb[i] += d.rgb[i];
        }

        public RGBPointVector clone()
        {
            return new RGBPointVector(rgb, x, y);
        }

        public double distance(RGBPointVector v2)
        {
            double d = 0.0D;
            for (int i = 0; i < rgb.Length; i++)
                d += (rgb[i] - v2.rgb[i]) * (rgb[i] - v2.rgb[i]);
            return Math.Sqrt(d);
        }
    }

    

    class ImageSegmentation
    {
        Bitmap bmp;
        int numberOfClusters;
        Color contoursColor;
        double epsilon;
        double m;
        int numberOfIterations;

        public FuzzyISODataForm parentForm;
        public Clustering clustering;

        public ImageSegmentation(Bitmap bmp, int numberOfClusters, Color contoursColor, double epsilon, double m, int numberOfIterations)
        {
            this.bmp = bmp;
            this.numberOfClusters = numberOfClusters;
            this.numberOfIterations = numberOfIterations;
            this.contoursColor = contoursColor;
            this.epsilon = epsilon;
            this.m = m;
        }

        public Bitmap run()
        {
            if (clustering == null)
                return null;

            RGBPointVector[] rgbPointVector = new RGBPointVector[bmp.Width * bmp.Height];
            int index = 0;
            for (int x = 0; x < bmp.Width; x++)
                for (int y = 0; y < bmp.Height; y++)
                    rgbPointVector[index++] = new RGBPointVector(bmp.GetPixel(x, y), x, y);

            ClusterSet cSet = new ClusterSet(rgbPointVector, numberOfClusters);

            clustering.clustering(cSet, parentForm);

            Bitmap bmp2 = new Bitmap(bmp.Width, bmp.Height);
            for (int i = 0; i < cSet.getNumberOfSamples(); i++)
            {
                RGBPointVector vector = (RGBPointVector)cSet.getVector(i);
                
                bmp2.SetPixel(vector.x, vector.y, cSet.getCluster(i));
            }


            Bitmap newImage2 = new Bitmap(bmp.Width, bmp.Height);
            for (int x = 1; x < bmp2.Width - 1; x++)
            {
                
                for (int y = 1; y < bmp2.Height - 1; y++)
                    if (bmp2.GetPixel(x, y) != bmp2.GetPixel(x - 1, y) || bmp2.GetPixel(x, y) != bmp2.GetPixel(x + 1, y) || bmp2.GetPixel(x, y) != bmp2.GetPixel(x, y - 1) || bmp2.GetPixel(x, y) != bmp2.GetPixel(x, y + 1))
                        newImage2.SetPixel(x, y, contoursColor);
                    else
                        newImage2.SetPixel(x, y, bmp.GetPixel(x, y));

            }

            return newImage2;
        }
    }

    public abstract class Clustering
    {
        public int numberOfIterations;
        public double epsilon;
        public double m;

        public double[][] copy(double[][] d)
        {
            double[][] arr = new double[d.Length][];
            for (int i = 0; i < arr.Length; i++)
            {
                arr[i] = new double[d[i].Length];
                for (int j = 0; j < arr[i].Length; j++)
                    arr[i][j] = d[i][j];
            }
            return arr;
        }
        public abstract void clustering(ClusterSet clustersSet, FuzzyISODataForm parentForm);
        

        public double[][] sub(double[][] d1, double[][] d2)
        {
            double[][] r = new double[d1.Length][];
            for (int i = 0; i < r.Length; i++)
            {
                r[i] = new double[d1[i].Length];
                for (int j = 0; j < r[i].Length; j++)
                    r[i][j] = d1[i][j] - d2[i][j];
            }
            return r;
        }

        public double calculate3rdNorm(double[][] d)
        {
            double sum = 0.0D;
            for (int i = 0; i < d.Length; i++)
            {
                for (int j = 0; j < d[i].Length; j++)
                    sum += d[i][j] * d[i][j];
            }

            return Math.Sqrt(sum);
        }
    }

    public class FuzzyISODataClustering : Clustering
    {
        public override void clustering(ClusterSet clustersSet, FuzzyISODataForm parentForm)
        {

            for (int i = 0; i < this.numberOfIterations; i++)
            {
                // inicjalizacja poczatkowej macierzy podzialu
                double[][] oldMU = copy(clustersSet.mU);
                // obliczenie srodkow grup
                RGBPointVector[] clustersCenters = clustersSet.getClustersCenters(this.m);

                // zmienne potrzebne w obliczeniach
                int[] Ik = new int[clustersCenters.Length];
                int l;

                // dla kazdego wektora
                for (int j = 0; j < clustersSet.getNumberOfSamples(); j++)
                {
                    // inicjalizacja zbioru Ik
                    for (l = 0; l < clustersCenters.Length; l++)
                        Ik[l] = -1;
                    // inicjalizacja licznika
                    l = 0;
                    // tablica odleglosci wektorow od srodkow grup
                    double[] dtab = new double[clustersCenters.Length];
                    // suma potrzebna do aktualizacji macierzy przynaleznosci
                    double sum = 0.0;
                    // dla wszystkich grup
                    for (int k = 0; k < clustersCenters.Length; k++)
                    {
                        // wyznaczanie odleglosci wektora od srodka grupy
                        dtab[k] = clustersCenters[k].distance(clustersSet.getVector(j));
                        // tworzenie zbioru Ik
                        if (dtab[k] == 0)
                        {
                            Ik[l] = k;
                            l++;
                        }
                        // wyliczenie sumy
                        sum += Math.Pow(1 / dtab[k], 2 / (this.m - 1));
                    }
                    // zmienne pomocnicze
                    double val;
                    bool flag;
                    // dla wszystkich grup
                    for (int k = 0; k < clustersCenters.Length; k++)
                    {
                        flag = false;
                        // jesli Ik = 0
                        if (Ik[0] == -1)
                        {
                            val = Math.Pow(1 / dtab[k], 2 / (this.m - 1)) / sum;
                        }
                        else
                        {
                            // sprawdzenie, czy k nalezy do Ik
                            for (l = 0; l < clustersCenters.Length; l++)
                                if (Ik[l] == k)
                                {
                                    flag = true;
                                    break;
                                }
                            // ustawienie odpowiedniej wartosci w zaleznosci od tego, czy nalezy, czy nie
                            val = flag ? 1.0 : 0.0;
                        }
                        // aktualizacja macierzy przynaleznosci
                        clustersSet.mU[j][k] = val;
                    }
                }

                // sprawdzenie, czy obliczenia powoduja zmiany macierzy podzialu
                oldMU = sub(oldMU, clustersSet.mU);
                double norm = calculate3rdNorm(oldMU);
                // sprawdzenie warunkow wyjscia


                if (parentForm != null)
                {
                    parentForm.Invoke(parentForm.incDelegate);
                }


                if (norm < epsilon)
                    break;
            }
        }
    }
    public class ISODataClustering : Clustering
    {
        public override void clustering(ClusterSet clustersSet, FuzzyISODataForm parentForm)
        {

            for (int i = 0; i < this.numberOfIterations; i++)
            {
                double[][] oldMU = copy(clustersSet.mU);
                RGBPointVector[] clustersCenters = clustersSet.getClustersCenters(1);
                
                for (int j = 0; j < clustersSet.getNumberOfSamples(); j++)
                {
                    int nearestCluster = 0;
                    double dmin = Double.MaxValue;
                    for (int k = 0; k < clustersCenters.Length; k++)
                    {
                        double d = clustersCenters[k].distance(clustersSet.getVector(j));
                        if (d < dmin)
                        {
                            nearestCluster = k;
                            dmin = d;
                        }
                    }

                    clustersSet.setCluster(j, nearestCluster);
                }

                oldMU = sub(oldMU, clustersSet.mU);
                double norm = calculate3rdNorm(oldMU);

                if (parentForm != null)
                {
                    parentForm.Invoke(parentForm.incDelegate);
                }
                if (norm < epsilon)
                    break;

            }
        
        }
    }


}